package com.nulldev.util.web.YouTubeExtractor.metadata;

import java.util.Collections;
import java.util.List;

import com.nulldev.util.VariableAPI.MathUtil;
import com.nulldev.util.VariableAPI.StringsUtil;
import com.nulldev.util.concurrency.threadIt.v4.emapi.uft.Executable;
import com.nulldev.util.data.Arrays.arrays.FastLists;
import com.nulldev.util.internal.backport.concurrency9.Lists;
import com.nulldev.util.logging.Logger;
import com.nulldev.util.logging.LoggerFactory;
import com.nulldev.util.manage.NonStandard;
import com.nulldev.util.web.YouTubeExtractor.clarifications.Clarification;
import com.nulldev.util.web.YouTubeExtractor.endscreen.EndscreenEntry;
import com.nulldev.util.web.YouTubeExtractor.items.YouTubeChapterEntry;
import com.nulldev.util.web.YouTubeExtractor.items.YouTubeVideoHeatmap;
import com.nulldev.util.web.YouTubeExtractor.related.VideoInfo;
import com.nulldev.util.web.YouTubeExtractor.subtitles.YouTubeCaptions;
import com.nulldev.util.web.urlDetector.Url;
import com.nulldev.util.web.urlDetector.detection.UrlDetector;
import com.nulldev.util.web.urlDetector.detection.UrlDetectorOptions;

public class YouTubeMetadata {
	private String videoID;

	private String title, author, authorID;
	private long views, duration;

	private double rating;
	private long likes = -1, dislikes = -1;

	private final String[] tags;

	private String description = "A description wasn't found.";
	private boolean isShortDescription;

	private String pageMap;
	private boolean limitedExtraction;
	private boolean is360;
	private int thumbnailLevel = -1;
	/**
	 * If true: God help you.
	 */
	private boolean isVR180;
	private List<YouTubeCaptions> captions = FastLists.list(0);
	private final List<VideoInfo> videoInfos = FastLists.list(0);
	private List<EndscreenEntry> en_entries = FastLists.list(0);
	private List<Clarification> clarifications = FastLists.list(0);
	private List<YouTubeChapterEntry> chapters;
	private List<String> superTags;
	protected final ExtendedMetadata emeta = new ExtendedMetadata();
	private String storyboardURL, rawStoryboardURL, rawCaptionURL;

	public class ExtendedMetadata {
		public String uploadDate;
		public String publishDate;
		public boolean isFamilyFriendly = true;
		public float loudnessDbRaw = 0f;
		public float perceptualLoudnessDb = 0f;
		public double minBufferTime = 5;

		public boolean hasUploadDate() {
			return this.uploadDate != null && !this.uploadDate.isEmpty();
		}

		public boolean hasPublishDate() {
			return this.publishDate != null && !this.publishDate.isEmpty();
		}

		public boolean isFamilyFriendly() {
			return this.isFamilyFriendly;
		}

		public double getLoudnessDB() {
			return this.loudnessDbRaw;
		}

		public double getPerceptualLoudnessDB() {
			return this.perceptualLoudnessDb;
		}

		public double getMinBufferTime() {
			return this.minBufferTime;
		}
	}

	public YouTubeMetadata(final String videoID, final String title, final String author, final String authorID, final long views, final long duration,
			final double rating, final long likes, final long dislikes, final boolean is360, final String[] tags, final List<YouTubeCaptions> captions) {
		this.videoID = videoID;
		this.title = title;
		this.author = author;
		this.authorID = authorID;
		this.views = views;
		this.duration = duration;
		this.rating = rating;
		this.likes = likes;
		this.dislikes = dislikes;
		this.tags = tags;
		this.captions = captions;
		this.is360 = is360;
	}

	public static enum ThumbnailSizeOption {

		/**
		 * Maximum Res, but in the WEBP format. <br>
		 * Usually up to 1920x1080
		 */
		MAX_RES_DEFAULT_WEBP(6, "maxresdefault", 1920, 1080),

		/**
		 * Maximum Res. <br>
		 * Usually up to 1280x720 (sometimes 1920x1080)
		 */
		MAX_RES_DEAFULT(5, "maxresdefault", 1280, 720),
		/**
		 * Standard Quality <br>
		 * Usually up to 640x480
		 */
		SD_DEFAULT(4, "sddefault", 640, 480),
		/**
		 * High Quality 2 <br>
		 * I have no idea what this is but it uses WebP so I don't like it. <br>
		 * Usually up to 480x360? <br>
		 * Not a JPEG.
		 */
		HQ2_DEFAULT(3, "hq2", 480, 360),
		/**
		 * High Quality <br>
		 * Usually up to 480x360
		 */
		HQ_DEFAULT(2, "hqdefault", 480, 360),
		/**
		 * Medium Quality <br>
		 * Usually up to 320x180
		 */
		MQ_DEFAULT(1, "mqdefault", 320, 180),
		/**
		 * Small Thumbnail <br>
		 * 120x90 / 43x20
		 */
		THUMBNAIL(0, "default", 120, 90);

		private final String str;
		private final int level, width, height;

		private ThumbnailSizeOption(final int level, final String str, final int width, final int height) {
			this.str = str;
			this.level = level;
			this.width = width;
			this.height = height;
		}

		public String getString() {
			return this.str;
		}

		public int getLevel() {
			return this.level;
		}

		public int width() {
			return this.width;
		}

		public int height() {
			return this.height;
		}

		public static ThumbnailSizeOption fromLevel(final int level) {
			for (final ThumbnailSizeOption val : values()) {
				if (val.level == level)
					return val;
			}
			return null;
		}

		public static int getLevel(final String url) {
			if (url == null || url.isEmpty())
				return THUMBNAIL.level;
			if (url.contains("/default"))
				return THUMBNAIL.level;
			if (url.contains("/hqdefault"))
				return HQ_DEFAULT.level;
			if (url.contains("/hq2"))
				return HQ2_DEFAULT.level;
			if (url.contains("/mqdefault"))
				return MQ_DEFAULT.level;
			if (url.contains("/sddefault"))
				return SD_DEFAULT.level;
			if (url.contains("/maxresdefault.webp"))
				return MAX_RES_DEFAULT_WEBP.level;
			if (url.contains("/maxresdefault"))
				return MAX_RES_DEAFULT.level;
			logger.warn("getLevel: Unknown level for URL: " + url);
			return THUMBNAIL.level;
		}

		public static String getThumbnail(final String videoID, final ThumbnailSizeOption size) {
			if (size == ThumbnailSizeOption.MAX_RES_DEFAULT_WEBP) {
				return "https://i.ytimg.com/vi_webp/" + videoID + "/" + size.getString() + ".webp";
			} else {
				return "https://i.ytimg.com/vi/" + videoID + "/" + size.getString() + ".jpg";
			}
		}

		private static final Logger logger = LoggerFactory.getLogger();
	}

	public String getTitle() {
		return this.title;
	}

	public String getThumbnail() {
		return this.getThumbnail(ThumbnailSizeOption.MAX_RES_DEAFULT);
	}

	public String getThumbnail(final ThumbnailSizeOption size) {
		if (size == ThumbnailSizeOption.MAX_RES_DEFAULT_WEBP) {
			return "https://i.ytimg.com/vi_webp/" + this.videoID + "/" + size.getString() + ".webp";
		} else {
			return "https://i.ytimg.com/vi/" + this.videoID + "/" + size.getString() + ".jpg";
		}
	}

	@Override
	public String toString() {
		return "YouTubeMetadata[title=" + this.title + ",author=" + this.author + ",authorID=" + this.authorID + ",duration="
				+ MathUtil.TimeUtil.formatSecondsL(this.duration) + ",rating=" + this.rating + ",clarifications=" + this.clarifications + "]";
	}

	public long getViewCount() {
		return this.views;
	}

	public double getRating() {
		return this.rating;
	}

	public boolean hasLikeData() {
		return (this.dislikes != -1 || this.likes != -1);
	}

	public long getLikeCount() {
		return this.likes;
	}

	public long getDislikeCount() {
		return this.dislikes;
	}

	public long getTotalLDLCount() {
		return this.dislikes + this.likes;
	}

	public String[] getTags() {
		return this.tags;
	}

	public String getTagsFormatted() {
		final StringBuilder sb = new StringBuilder();
		final String[] tags = getTags();
		for (int i = 0; i < tags.length; i++) {
			if (i == tags.length - 1) {
				sb.append(tags[i]);
			} else {
				sb.append(tags[i] + ", ");
			}
		}
		return sb.toString();
	}

	public String getDescription() {
		return this.description;
	}

	public String getEnhancedDescription() {
		return getEnhancedDescription(false);
	}

	public String getEnhancedDescription(final boolean enableLocalRedir) {
		final UrlDetector urld = new UrlDetector(this.description, UrlDetectorOptions.HTML);
		String desc = this.description;
		for (final Url u : urld.detect()) {
			final String furl = u.getFullUrl();
			if (enableLocalRedir && furl.startsWith("https://www.youtube.com/playlist")) {
				final String fixed_furl = furl.replace("https://www.youtube.com/playlist", "/playlist");
				desc = StringsUtil.replace(desc, furl, "<a href=\"" + fixed_furl + "\">" + furl + "</a>");
			} else {
				desc = StringsUtil.replace(desc, furl, "<a href=\"" + furl + "\">" + furl + "</a>");
			}
		}
		return desc;
	}

	public void setDescription(final String description) {
		this.description = description;
	}

	public boolean isShortDescription() {
		return this.isShortDescription;
	}

	public void setShortDescription(final boolean value) {
		this.isShortDescription = value;
	}

	public long getDuration() {
		return this.duration;
	}

	public String getAuthor() {
		return this.author;
	}

	public String getAuthorID() {
		return this.authorID;
	}

	public boolean isLiveStream() {
		return this.isLiveStream;
	}

	private boolean isLiveStream;

	public YouTubeMetadata setLiveStream(final boolean value) {
		this.isLiveStream = value;
		return this;
	}

	public boolean isDVRStream() {
		return this.isDVRStream;
	}

	private boolean isDVRStream;

	public YouTubeMetadata setDVRStream(final boolean value) {
		this.isDVRStream = value;
		return this;
	}

	public void setMap(final String data) {
		this.pageMap = data;
	}

	public String getPageData() {
		return this.pageMap;
	}

	public boolean hasPageData() {
		return this.pageMap != null && !this.pageMap.isEmpty();
	}

	public void limitedExtraction(final boolean value) {
		this.limitedExtraction = value;
	}

	public boolean limitedExtraction() {
		return this.limitedExtraction;
	}

	public boolean hasCaptions() {
		return this.captions != null && !this.captions.isEmpty();
	}

	public List<YouTubeCaptions> getCaptions() {
		return this.captions;
	}

	public boolean is360() {
		return this.is360;
	}

	public boolean isVR180() {
		return this.isVR180;
	}

	public YouTubeMetadata setVR180(final boolean value) {
		this.isVR180 = value;
		return this;
	}

	@NonStandard
	public void clear() {
		this.captions.clear();
		this.en_entries.clear();
		this.videoInfos.clear();
		this.clarifications.clear();
		this.pageMap = null;
	}

	public void setRelatedVideoOverride(final List<VideoInfo> videoInfos) {
		this.videoInfos.addAll(videoInfos);
	}

	public boolean hasRelatedVideoOverride() {
		return !this.videoInfos.isEmpty();
	}

	public List<VideoInfo> getRelatedVideoOverrides() {
		return this.videoInfos;
	}

	public YouTubeMetadata setEndscreenData(final List<EndscreenEntry> entries) {
		this.en_entries = entries;
		return this;
	}

	public boolean hasEndscreenEntries() {
		return this.en_entries != null && !this.en_entries.isEmpty();
	}

	public List<EndscreenEntry> getEndscreenEntries() {
		return this.en_entries;
	}

	public ExtendedMetadata getExtendedMetadata() {
		return this.emeta;
	}

	/* ABI(4500) */

	public boolean hasClarifications() {
		return this.clarifications != null && !this.clarifications.isEmpty();
	}

	public List<Clarification> getClarifications() {
		return this.clarifications;
	}

	public void addClarification(final Clarification c) {
		if (c != null) {
			this.clarifications.add(c);
		}
	}

	/* ABI(4510) */

	public boolean hasChapters() {
		return this.chapters != null && !this.chapters.isEmpty();
	}

	public YouTubeMetadata setChapters(final List<YouTubeChapterEntry> yt_chapters) {
		this.chapters = yt_chapters;
		return this;
	}

	public List<YouTubeChapterEntry> getChapters() {
		return this.chapters;
	}

	/* ABI(4520) */

	public YouTubeMetadata setSupertags(final List<String> supertags_entries) {
		this.superTags = supertags_entries;
		return this;
	}

	public boolean hasSupertags() {
		return this.superTags != null && !this.superTags.isEmpty();
	}

	public List<String> getSupertags() {
		return this.superTags;
	}

	/* ABI(5000) */

	public YouTubeMetadata setStoryboardURL(final String url) {
		this.storyboardURL = url;
		return this;
	}

	public boolean hasStoryboardURL() {
		return this.storyboardURL != null && !this.storyboardURL.isEmpty();
	}

	public String getStoryboardURL() {
		return this.storyboardURL;
	}

	/* ABI(5010) */

	public YouTubeMetadata setRawStoryboardURL(final String url) {
		this.rawStoryboardURL = url;
		return this;
	}

	public boolean hasRawStoryboardURL() {
		return this.rawStoryboardURL != null && !this.rawStoryboardURL.isEmpty();
	}

	public String getRawStoryboardURL() {
		return this.rawStoryboardURL;
	}

	/* ABI(5020) */

	public YouTubeMetadata setThumbnailLevel(final int level) {
		if (level < 0)
			return this;
		this.thumbnailLevel = level;
		return this;
	}

	public boolean hasThumbnailLevel() {
		return this.thumbnailLevel >= 0;
	}

	public int getThumbnailLevel() {
		return this.thumbnailLevel;
	}

	/* ABI(5030) */

	public void setRawCaptionsURL(final String string) {
		if (string != null && !string.isEmpty())
			this.rawCaptionURL = string;
	}

	public boolean hasRawCaptionsURL() {
		return this.rawCaptionURL != null && !this.rawCaptionURL.isEmpty();
	}

	public String getRawCaptionsURL() {
		return this.rawCaptionURL;
	}

	/* ABI(5300) */

	public static class DRMMetadata {
		public static enum DRMFamily {
			WIDEVINE,
			PLAYREADY,
			FAIRPLAY,
			UNKNOWN;

			public static DRMFamily getFamily(String family) {
				for (final DRMFamily f : values()) {
					if (f.name().equals(family))
						return f;
				}
				return UNKNOWN;
			}
		}

		public static class LicenseInfo {
			private final DRMFamily family;
			private final String url;

			public String getURL() {
				return this.url;
			}

			public DRMFamily getFamily() {
				return this.family;
			}

			public LicenseInfo(final String url) {
				this.url = url;
				this.family = DRMFamily.UNKNOWN;
			}

			public LicenseInfo(final String url, final String family) {
				this.url = url;
				this.family = DRMFamily.getFamily(family);
			}
		}

		private List<LicenseInfo> licenseInfos = FastLists.list(0);
		private String drmParams, dashManifestUrl;

		public boolean hasDASHManifest() {
			return this.dashManifestUrl != null && !this.dashManifestUrl.isEmpty();
		}

		public boolean hasDRMParams() {
			return this.drmParams != null && !this.drmParams.isEmpty();
		}

		public String getDASHManifest() {
			return this.dashManifestUrl;
		}

		public String getDRMParams() {
			return this.drmParams;
		}

		public boolean hasLicenseInfos() {
			return !this.licenseInfos.isEmpty();
		}

		public List<LicenseInfo> getLicenseInfos() {
			return Collections.unmodifiableList(this.licenseInfos);
		}

		public DRMMetadata(final String drmParams, final String dashManifest, final LicenseInfo... infos) {
			this.drmParams = drmParams;
			this.dashManifestUrl = dashManifest;
			this.licenseInfos.addAll(Lists.of(infos));
		}

		private String widevineServiceCert;
		private boolean useInnertubeDrmService;

		public String getWidevineCertificate() {
			return this.widevineServiceCert;
		}

		public boolean hasWidevineCertificate() {
			return this.widevineServiceCert != null && !this.widevineServiceCert.isEmpty();
		}

		public DRMMetadata setWidevineCertificate(final String cert) {
			this.widevineServiceCert = cert;
			return this;
		}

		public DRMMetadata setUseInnertubeDrmService(final boolean value) {
			this.useInnertubeDrmService = value;
			return this;
		}

		public boolean useInnertubeDrmService() {
			return this.useInnertubeDrmService;
		}
	}

	private DRMMetadata drmMetadata;

	public boolean hasDRMMetadata() {
		return this.drmMetadata != null;
	}

	public YouTubeMetadata setDRMMetadata(final DRMMetadata drm) {
		this.drmMetadata = drm;
		return this;
	}

	public DRMMetadata getDRMMetadata() {
		return this.drmMetadata;
	}

	/* ABI (5510) */

	public static enum PlaybackMode {
		STANDARD("standard"),
		YT_MUSIC("yt_music"),
		YT_SHORT("yt_short"),
		VIDEO_360("video_360"),
		UNKNOWN("unknown"),

		/* Non-standard. */
		NTUBE_ARCHIVE_VIEWER("ntube_archive_viewer"),

		;

		public final String id;

		private PlaybackMode(final String id) {
			this.id = id;
		}
	}

	private PlaybackMode playbackMode = PlaybackMode.STANDARD;

	public boolean hasPlaybackMode() {
		return this.playbackMode != null;
	}

	public PlaybackMode getPlaybackMode() {
		return this.playbackMode;
	}

	public YouTubeMetadata setPlaybackMode(final PlaybackMode mode) {
		if (mode == null)
			return this;
		this.playbackMode = mode;
		return this;
	}

	public void updateLikeCount(final long likes) {
		if (likes < -1)
			return;
		this.likes = likes;
	}

	/* ABI (6050) */

	private Executable<YouTubeVideoHeatmap> heatmap_exec;

	public boolean hasHeatmap() {
		return this.heatmap_exec != null;
	}

	public Executable<YouTubeVideoHeatmap> getHeatmap() {
		return this.heatmap_exec;
	}

	public void setHeatmap(final Executable<YouTubeVideoHeatmap> heatmap_exec) {
		this.heatmap_exec = heatmap_exec;
	}

	/* ABI (6055) */

	private long content_start_time = -1L;

	public YouTubeMetadata setContentStartTime(final long time) {
		this.content_start_time = time;
		return this;
	}

	public boolean hasContentStartTime() {
		return this.content_start_time > 0L;
	}

	public long getContentStartTime() {
		return this.content_start_time;
	}

	/* ABI (6060) */

	private String localized_title = null;

	public String getLocalizedTitle() {
		return this.localized_title;
	}

	public boolean hasLocalizedTitle() {
		return this.localized_title != null && !this.localized_title.isEmpty();
	}

	public YouTubeMetadata setLocalizedTitle(final String localized_title) {
		this.localized_title = localized_title;
		return this;
	}

	/* ABI (6070) */

	private boolean isPartialMetadata = false;

	public boolean isPartialMetadata() {
		return this.isPartialMetadata;
	}

	public YouTubeMetadata setPartialMetadata(final boolean partial) {
		this.isPartialMetadata = partial;
		return this;
	}

	/* */

	private boolean isLowLatencyStream;

	public boolean isLowLatencyStream() {
		return this.isLowLatencyStream;
	}

	public YouTubeMetadata setLowLatencyStream(final boolean ll) {
		this.isLowLatencyStream = ll;
		return this;
	}
}
